﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Security.Principal;
using System.Text;
using FlexiCommerce.Collections;
using FlexiCommerce.Details;

namespace FlexiCommerce.Components
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Reflection;
    using System.Security.Principal;
    using System.Text;
    using FlexiCommerce.Collections;
    using FlexiCommerce.Details;
    using System.ComponentModel.DataAnnotations;
    using System.Web.DomainServices;

    [Serializable, DebuggerDisplay("{Name}#{ID}")]
    public abstract partial class GenericPersistenceItem : HierarchicalPersistenceItem, IPersistenceItemLife, IComparable, IComparable<GenericPersistenceItem>, ICloneable
    {
        #region Private Fields

        private string title;
        private string name;
        private string zoneName;
        private DateTime created;
        private DateTime updated;
        private DateTime? expires = null;
        private int sortOrder;
        [Persistence.DoNotCopy]
        private string url = null;
        private bool visible = true;
        private string savedBy;
        [Persistence.DoNotCopy]
        private IDictionary<string, Details.ContentDetail> details = new Dictionary<string, Details.ContentDetail>();
        [Persistence.DoNotCopy]
        private IDictionary<string, Details.DetailCollection> detailCollections = new Dictionary<string, Details.DetailCollection>();

        #endregion

        #region Constructor
        /// <summary>Creates a new instance of the GenericPersistenceItem.</summary>
        public GenericPersistenceItem()
        {
            created = DateTime.Now;
            updated = DateTime.Now;
        }
        #endregion

        #region Persisted Properties

        public virtual string Title
        {
            get { return title; }
            set { title = value; }
        }

        private static char[] invalidCharacters = new char[] { '%', '?', '&', '/', ':' };
        /// <summary>Gets or sets the item's name. This is used to compute the item's url and can be used to uniquely identify the item among other items on the same level.</summary>
        public virtual string Name
        {
            get
            {
                return name ?? (ID > 0 ? ID.ToString() : null);
            }
            set
            {
                //if (value != null && value.IndexOfAny(invalidCharacters) >= 0) throw new N2Exception("Invalid characters in name, '%', '?', '&', '/', ':', '+', '.' not allowed.");
                if (string.IsNullOrEmpty(value))
                    name = null;
                else
                    name = value;
                url = null;
            }
        }

        public virtual string ZoneName
        {
            get { return zoneName; }
            set { zoneName = value; }
        }

        public virtual DateTime Created
        {
            get { return created; }
            set { created = value; }
        }

        public virtual DateTime Updated
        {
            get { return updated; }
            set { updated = value; }
        }

        public virtual DateTime? Expires
        {
            get { return expires; }
            set { expires = value != DateTime.MinValue ? value : null; }
        }

        public virtual int SortOrder
        {
            get { return sortOrder; }
            set { sortOrder = value; }
        }

        public virtual bool Visible
        {
            get { return visible; }
            set { visible = value; }
        }

        public virtual string SavedBy
        {
            get { return savedBy; }
            set { savedBy = value; }
        }

        /// <summary>Gets or sets the details collection. These are usually accessed using the e.g. item["Detailname"]. This is a place to store content data.</summary>
        [Exclude]
        public virtual IDictionary<string, Details.ContentDetail> Details
        {
            get { return details; }
            set { details = value; }
        }

        /// <summary>Gets or sets the details collection collection. These are details grouped into a collection.</summary>
        [Exclude]
        public virtual IDictionary<string, Details.DetailCollection> DetailCollections
        {
            get { return detailCollections; }
            set { detailCollections = value; }
        }

        #endregion


        #region this[]

        /// <summary>Gets or sets the detail or property with the supplied name. If a property with the supplied name exists this is always returned in favour of any detail that might have the same name.</summary>
        /// <param name="detailName">The name of the propery or detail.</param>
        /// <returns>The value of the property or detail. If now property exists null is returned.</returns>
        public override object this[string detailName]
        {
            get
            {
                if (detailName == null)
                    throw new ArgumentNullException("detailName");

                object value = base[detailName];
                if (value == null)
                {
                    switch (detailName)
                    {
                        case "Title":
                            return Title;
                        case "Name":
                            return Name;
                        default:
                            return Utility.Evaluate(this, detailName)
                                   ?? GetDetail(detailName)
                                   ?? GetDetailCollection(detailName, false);
                    }
                }
                else
                {
                    return value;
                }
            }
            set
            {

                try
                {
                    base[detailName] = value;
                }
                catch (FlexiException)
                {
                    if (value is Details.DetailCollection)
                        throw new FlexiException("Cannot set a detail collection this way, add it to the DetailCollections collection instead.");
                    else
                    {
                        SetDetail(detailName, value);
                    }
                }


            }
        }
        #endregion

        #region GetDetail & SetDetail<T> Methods
        /// <summary>Gets a detail from the details bag.</summary>
        /// <param name="detailName">The name of the value to get.</param>
        /// <returns>The value stored in the details bag or null if no item was found.</returns>
        public virtual object GetDetail(string detailName)
        {
            return Details.ContainsKey(detailName)
                ? Details[detailName].Value
                : null;
        }

        /// <summary>Gets a detail from the details bag.</summary>
        /// <param name="detailName">The name of the value to get.</param>
        /// <param name="defaultValue">The default value to return when no detail is found.</param>
        /// <returns>The value stored in the details bag or null if no item was found.</returns>
        public virtual T GetDetail<T>(string detailName, T defaultValue)
        {
            return Details.ContainsKey(detailName)
                ? (T)Details[detailName].Value
                : defaultValue;
        }

        /// <summary>Set a value into the <see cref="Details"/> bag. If a value with the same name already exists it is overwritten. If the value equals the default value it will be removed from the details bag.</summary>
        /// <param name="detailName">The name of the item to set.</param>
        /// <param name="value">The value to set. If this parameter is null or equal to defaultValue the detail is removed.</param>
        /// <param name="defaultValue">The default value. If the value is equal to this value the detail will be removed.</param>
        protected virtual void SetDetail<T>(string detailName, T value, T defaultValue)
        {
            if (value == null || !value.Equals(defaultValue))
            {
                SetDetail<T>(detailName, value);
            }
            else if (Details.ContainsKey(detailName))
            {
                details.Remove(detailName);
            }
        }

        /// <summary>Set a value into the <see cref="Details"/> bag. If a value with the same name already exists it is overwritten.</summary>
        /// <param name="detailName">The name of the item to set.</param>
        /// <param name="value">The value to set. If this parameter is null the detail is removed.</param>
        protected virtual void SetDetail<T>(string detailName, T value)
        {
            Details.ContentDetail detail = Details.ContainsKey(detailName) ? Details[detailName] : null;

            if (detail != null && value != null && typeof(T).IsAssignableFrom(detail.ValueType))
            {
                // update an existing detail
                detail.Value = value;
            }
            else
            {
                if (detail != null)
                    // delete detail or remove detail of wrong type
                    Details.Remove(detailName);
                if (value != null)
                    // add new detail
                    Details.Add(detailName, FlexiCommerce.Details.ContentDetail.New(this, detailName, value));
            }
        }
        #endregion

        #region GetDetailCollection
        /// <summary>Gets a named detail collection.</summary>
        /// <param name="collectionName">The name of the detail collection to get.</param>
        /// <param name="createWhenEmpty">Wether a new collection should be created if none exists. Setting this to false means null will be returned if no collection exists.</param>
        /// <returns>A new or existing detail collection or null if the createWhenEmpty parameter is false and no collection with the given name exists..</returns>
        public virtual Details.DetailCollection GetDetailCollection(string collectionName, bool createWhenEmpty)
        {
            if (DetailCollections.ContainsKey(collectionName))
                return DetailCollections[collectionName];
            else if (createWhenEmpty)
            {
                Details.DetailCollection collection = new Details.DetailCollection(this, collectionName);
                DetailCollections.Add(collectionName, collection);
                return collection;
            }
            else
                return null;
        }
        #endregion

        #region AddTo & GetChild & GetChildren

        private const int SortOrderTreshold = 9999;

        /// <summary>Adds an item to the children of this item updating it's parent refernce.</summary>
        /// <param name="newParent">The new parent of the item. If this parameter is null the item is detached from the hierarchical structure.</param>
        //public virtual void AddTo(HierarchalPersistenceItem newParent)
        //{
        //    if (Parent != null && Parent != newParent && Parent.Children.Contains(this as HierarchalPersistenceItem))
        //        Parent.Children.Remove(this as HierarchalPersistenceItem);

        //    url = null;
        //    Parent = newParent;

        //    if (newParent != null && !newParent.Children.Contains(this as HierarchalPersistenceItem))
        //    {
        //        IList<HierarchalPersistenceItem> siblings = newParent.Children;
        //        if (siblings.Count > 0)
        //        {
        //            int lastOrder = siblings[siblings.Count - 1].SortOrder;

        //            for (int i = siblings.Count - 2; i >= 0; i--)
        //            {
        //                if (siblings[i].SortOrder < lastOrder - SortOrderTreshold)
        //                {
        //                    siblings.Insert(i + 1, this as HierarchalPersistenceItem);
        //                    return;
        //                }
        //                lastOrder = siblings[i].SortOrder;
        //            }

        //            if (lastOrder > SortOrderTreshold)
        //            {
        //                siblings.Insert(0, this as HierarchalPersistenceItem);
        //                return;
        //            }
        //        }

        //        siblings.Add(this as HierarchalPersistenceItem);
        //    }
        //}


        /// <summary>Gets children applying filters.</summary>
        /// <param name="filters">The filters to apply on the children.</param>
        /// <returns>A list of filtered child items.</returns>
        public virtual ItemList GetChildren(params ItemFilter[] filters)
        {
            return GetChildren(new CompositeFilter(filters));
        }

        /// <summary>Gets children applying filters.</summary>
        /// <param name="filter">The filters to apply on the children.</param>
        /// <returns>A list of filtered child items.</returns>
        public virtual ItemList GetChildren(ItemFilter filter)
        {
            IEnumerable<HierarchicalPersistenceItem> items = Children;
            return new ItemList((IEnumerable<PersistenceItem>)items, filter);
        }

        #endregion

        #region IComparable & IComparable<GenericPersistenceItem> Members

        int IComparable.CompareTo(object obj)
        {
            if (obj is GenericPersistenceItem)
                return SortOrder - ((GenericPersistenceItem)obj).SortOrder;
            else
                return 0;
        }
        int IComparable<GenericPersistenceItem>.CompareTo(GenericPersistenceItem other)
        {
            return SortOrder - other.SortOrder;
        }

        #endregion

        #region ICloneable Members

        object ICloneable.Clone()
        {
            return Clone(true);
        }

        /// <summary>Creats a copy of this item including details and authorized roles resetting ID.</summary>
        /// <param name="includeChildren">Wether this item's child items also should be cloned.</param>
        /// <returns>The cloned item with or without cloned child items.</returns>
        public virtual GenericPersistenceItem Clone(bool includeChildren)
        {
            GenericPersistenceItem cloned = (GenericPersistenceItem)Activator.CreateInstance(GetType());
            cloned.title = title;
            cloned.name = name;
            cloned.zoneName = zoneName;
            cloned.created = created;
            cloned.updated = updated;
            cloned.expires = expires;
            cloned.sortOrder = sortOrder;
            cloned.visible = visible;
            cloned.savedBy = savedBy;

            CloneDetails(cloned);
            //CloneChildren(includeChildren, cloned);

            return cloned;
        }

        #region Clone Helper Methods

        //TODO: CloneChildren
        //private void CloneChildren(bool includeChildren, GenericPersistenceItem cloned)
        //{
        //    cloned.children = new List<HierarchalPersistenceItem>();
        //    if (includeChildren)
        //    {
        //        foreach (GenericPersistenceItem child in Children)
        //        {
        //            GenericPersistenceItem clonedChild = child.Clone(true);
        //            clonedChild.AddTo(cloned);
        //        }
        //    }
        //}

        private void CloneDetails(GenericPersistenceItem cloned)
        {
            cloned.details = new Dictionary<string, ContentDetail>();
            foreach (Details.ContentDetail detail in Details.Values)
            {
                cloned[detail.Name] = detail.Value;
            }

            cloned.detailCollections = new Dictionary<string, DetailCollection>();
            foreach (Details.DetailCollection collection in DetailCollections.Values)
            {
                Details.DetailCollection clonedCollection = collection.Clone();
                clonedCollection.EnclosingItem = cloned;
                cloned.DetailCollections[collection.Name] = clonedCollection;
            }
        }
        #endregion


        #endregion





        #region Equals, HashCode and ToString Overrides
        /// <summary>Checks the item with another for equality.</summary>
        /// <returns>True if two items have the same ID.</returns>


        
        /// <summary>Gets a hash code based on the ID.</summary>
        /// <returns>A hash code.</returns>
        
        #endregion
    }

}
